/*
 *  This file is part of the Image Metadata Editor.
 *
 *  Copyright (C) 2015 Australian Institute of Marine Science
 *
 *  Contact: Gael Lafond <g.lafond@aims.org.au>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package au.gov.aims.exif.editor;

import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBuilder;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.ColumnConstraintsBuilder;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.RowConstraintsBuilder;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.util.Callback;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

public class FXUtils {
	private static final Logger LOGGER = Logger.getLogger(FXUtils.class.getName());

	private static FXUtils.NotificationWindow notificationWindow = null;

	public static Node createApplicationHeader() {
		ImageView header = new ImageView();
		header.setImage(getResourceImage("header-aims-eatlas.jpg"));

		HBox headerBox = new HBox();
		headerBox.getChildren().add(header);
		headerBox.setStyle("-fx-background-color: #3d4547");

		return headerBox;
	}

	public static String getWindowTitle() {
		return getWindowTitle(null);
	}
	public static String getWindowTitle(String suffix) {
		return (suffix == null || suffix.isEmpty() ? "" : suffix + " - ") + "Image metadata editor";
	}

	public static Image getResourceImage(String resourceFileName) {
		InputStream imageInputStream = ImageEditor.class.getClassLoader().getResourceAsStream(resourceFileName);
		return new Image(imageInputStream);
	}

	public static FXUtils.NotificationWindow getNotificationWindow() {
		if (notificationWindow == null) {
			notificationWindow = new FXUtils.NotificationWindow();
		}
		return notificationWindow;
	}

	public static class NotificationWindow {
		private ListView<Text> notificationsListView = null;
		private Stage window = null;

		public void showError(final String error) {
			showMessage("ERROR: " + error);
		}
		public void showWarning(final String warning) {
			showMessage("WARNING: " + warning);
		}
		public void showMessage(final String message) {
			Text text = new Text(message);
			ListView<Text> listView = getNotificationListView();
			// Text do not seems to have a hgrow property... The binding has issues
			// (unnecessary horizontal scroller appear when resized smaller),
			// but that's the best I could find.
			text.wrappingWidthProperty().bind(listView.widthProperty().subtract(15));

			listView.getItems().add(text);
			this.toFront();
		}
		public void toFront() {
			if (this.window != null) {
				this.window.toFront();
			}
		}

		private ListView<Text> getNotificationListView() {
			if (this.notificationsListView == null) {
				this.window = new Stage();
				this.window.setTitle(FXUtils.getWindowTitle("Messages"));
				this.window.getIcons().add(FXUtils.getResourceImage("warning-icon.png"));
				this.window.initModality(Modality.WINDOW_MODAL);
				this.window.setMinWidth(200);
				this.window.setWidth(600);
				this.window.setMinHeight(150);
				this.window.setHeight(300);
				// Unset the notificationsListView when the window is closed,
				// so we know that the window has to be re-created next time.
				this.window.onCloseRequestProperty().set(new EventHandler<WindowEvent>() {
					public void handle(WindowEvent e) {
						NotificationWindow.this.notificationsListView = null;
						NotificationWindow.this.window = null;
					}
				});

				Button okButton = new Button("Ok");
				okButton.setCancelButton(true); // Trigger when ESC is pressed
				okButton.setOnAction(new EventHandler<ActionEvent>() {
					@Override
					public void handle(ActionEvent actionEvent) {
						// abort action and close the dialog.
						NotificationWindow.this.window.close();
						NotificationWindow.this.notificationsListView = null;
						NotificationWindow.this.window = null;
					}
				});

				this.notificationsListView = new ListView<Text>();

				VBox vbox = new VBox();
				vbox.setSpacing(15);
				vbox.setAlignment(Pos.BOTTOM_RIGHT);
				vbox.setPadding(new Insets(10, 10, 10, 10));
				VBox.setVgrow(this.notificationsListView, Priority.ALWAYS);
				vbox.getChildren().addAll(this.notificationsListView, okButton);

				this.window.setScene(new Scene(vbox));
				this.window.show();
			}
			return this.notificationsListView;
		}
	}

	public abstract static class ConfirmWindow extends Stage {
		public ConfirmWindow(final Stage primaryStage, final String titleSuffix, final String message, final double windowWidth) {
			this.setTitle(FXUtils.getWindowTitle(titleSuffix));
			this.initModality(Modality.WINDOW_MODAL);
			this.initOwner(primaryStage);
			this.setMinWidth(200);
			this.setWidth(windowWidth);
			this.setMinHeight(150);

			HBox hbox = new HBox();
			hbox.setSpacing(10);
			hbox.setAlignment(Pos.BOTTOM_RIGHT);
			hbox.getChildren().addAll(
					ButtonBuilder.create().text("Yes").defaultButton(true).onAction(new EventHandler<ActionEvent>() {
						@Override
						public void handle(ActionEvent actionEvent) {
							// take action and close the dialog.
							ConfirmWindow.this.handle(actionEvent);
							ConfirmWindow.this.close();
						}
					}).build(),
					ButtonBuilder.create().text("No").cancelButton(true).onAction(new EventHandler<ActionEvent>() {
						@Override
						public void handle(ActionEvent actionEvent) {
							// abort action and close the dialog.
							ConfirmWindow.this.close();
						}
					}).build()
			);

			Label label = new Label(message);
			label.setWrapText(true);

			GridPane grid = new GridPane();
			grid.setPadding(new Insets(10, 10, 10, 10));
			grid.getColumnConstraints().addAll(
					ColumnConstraintsBuilder.create().hgrow(Priority.ALWAYS).build());
			grid.getRowConstraints().addAll(
					RowConstraintsBuilder.create().vgrow(Priority.ALWAYS).build());

			GridPane.setConstraints(label, 0, 0);
			GridPane.setValignment(label, VPos.TOP);
			GridPane.setHgrow(label, Priority.ALWAYS);

			GridPane.setConstraints(hbox, 0, 1);

			grid.getChildren().addAll(label, hbox);

			this.setScene(new Scene(grid));
		}

		public abstract void handle(ActionEvent actionEvent);
	}



	/**
	 * Enable right click and double click on list view items.
	 */
	public static class ListViewCallback<T> implements Callback<ListView<T>, ListCell<T>> {
		private ContextMenu contextMenu;
		private Callback<ListView<T>, ListCell<T>> cellFactory;
		private boolean doubleClickEnabled;

		public ListViewCallback(boolean doubleClickEnabled, ContextMenu contextMenu) {
			this(doubleClickEnabled, contextMenu, null);
		}

		public ListViewCallback(boolean doubleClickEnabled, ContextMenu contextMenu, Callback<ListView<T>, ListCell<T>> cellFactory) {
			this.doubleClickEnabled = doubleClickEnabled;
			this.contextMenu = contextMenu;
			this.cellFactory = cellFactory;
		}

		public void onDoubleClick() {}

		@Override public ListCell<T> call(ListView<T> listView) {
			ListCell<T> cell = this.cellFactory == null ? new DefaultListCell<T>() : this.cellFactory.call(listView);
			if (this.contextMenu != null) {
				cell.setContextMenu(this.contextMenu);
			}

			if (this.doubleClickEnabled) {
				cell.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
					@Override
					public void handle(MouseEvent event) {
						if (event.getClickCount() > 1) {
							ListViewCallback.this.onDoubleClick();
						}
					}
				});
			}

			return cell;
		}
	}
	public static class DefaultListCell<T> extends ListCell<T> {
		@Override public void updateItem(T item, boolean empty) {
			super.updateItem(item, empty);

			if (empty) {
				setText(null);
				setGraphic(null);
			} else if (item instanceof Node) {
				setText(null);
				Node currentNode = getGraphic();
				Node newNode = (Node) item;
				if (currentNode == null || ! currentNode.equals(newNode)) {
					setGraphic(newNode);
				}
			} else {
				setText(item == null ? "null" : item.toString());
				setGraphic(null);
			}
		}
	}

	/**
	 * Bunch of useful methods, not necessarily related to JavaFX.
	 */

	public static void inputStreamToFile(InputStream inputStream, File outputFile) throws IOException {
		if (inputStream != null && outputFile != null) {
			if (!outputFile.exists()) {
				outputFile.createNewFile();
			}

			OutputStream outputStream = null;

			try {
				// write the inputStream to a FileOutputStream
				outputStream = new FileOutputStream(outputFile);

				int read = 0;
				byte[] bytes = new byte[1024];

				while ((read = inputStream.read(bytes)) != -1) {
					outputStream.write(bytes, 0, read);
				}
			} finally {
				if (outputStream != null) {
					try {
						outputStream.close();
					} catch(Exception ex) {
						// Very unlikely to happen
						LOGGER.log(Level.WARNING, "Error occurred while closing the output stream", ex);
					}
				}
			}
		}
	}
}
